#include "extdll.h"
#include "util.h"
#include "cbase.h"
#include "player.h"
#include "client.h"

#include "pm_shared.h"

#include "utllinkedlist.h"

#include "game_shared/GameEvent.h"		// Game event enum used by career mode, tutor system, and bots
#include "game_shared/bot/bot_util.h"
#include "game_shared/bot/simple_state_machine.h"

#include "game_shared/steam_util.h"

#include "game_shared/bot/bot_manager.h"
#include "game_shared/bot/bot_constants.h"
#include "game_shared/bot/bot.h"

#include "game_shared/shared_util.h"
#include "game_shared/bot/bot_profile.h"

#include "game_shared/bot/nav.h"
#include "game_shared/bot/improv.h"
#include "game_shared/bot/nav_node.h"
#include "game_shared/bot/nav_area.h"
#include "game_shared/bot/nav_file.h"
#include "game_shared/bot/nav_path.h"

#include "globals.h"
#include "game_shared/simple_checksum.h"

namespace sv {

	/*
	* Globals initialization
	*/
	DLL_GLOBAL BotProfileManager* TheBotProfiles = NULL;
	DLL_GLOBAL const char* BotDifficultyName[] = { "EASY", "NORMAL", "HARD", "EXPERT", NULL };

	// Generates a filename-decorated skin name

	const char* GetDecoratedSkinName(const char* name, const char* filename)
	{
		const int BufLen = MAX_PATH + 64;
		static char buf[BufLen];
		Q_snprintf(buf, BufLen, "%s/%s", filename, name);
		return buf;
	}

	const char* BotProfile::GetWeaponPreferenceAsString(int i) const
	{
		if (i < 0 || i >= m_weaponPreferenceCount)
			return NULL;

		return WeaponIDToAlias(m_weaponPreference[i]);
	}

	// Return true if this profile has a primary weapon preference
	bool BotProfile::HasPrimaryPreference() const
	{
		for (int i = 0; i < m_weaponPreferenceCount; ++i)
		{
			int weaponClass = AliasToWeaponClass(WeaponIDToAlias(m_weaponPreference[i]));

			if (weaponClass == WEAPONCLASS_SUBMACHINEGUN ||
				weaponClass == WEAPONCLASS_SHOTGUN ||
				weaponClass == WEAPONCLASS_MACHINEGUN ||
				weaponClass == WEAPONCLASS_RIFLE ||
				weaponClass == WEAPONCLASS_SNIPERRIFLE)
				return true;
		}

		return false;
	}

	// Return true if this profile has a pistol weapon preference
	bool BotProfile::HasPistolPreference() const
	{
		for (int i = 0; i < m_weaponPreferenceCount; ++i)
		{
			if (AliasToWeaponClass(WeaponIDToAlias(m_weaponPreference[i])) == WEAPONCLASS_PISTOL)
				return true;
		}

		return false;
	}

	// Return true if this profile is valid for the specified team
	bool BotProfile::IsValidForTeam(BotProfileTeamType team) const
	{
		return (team == BOT_TEAM_ANY || m_teams == BOT_TEAM_ANY || team == m_teams);
	}

	BotProfileManager::BotProfileManager()
	{
		m_nextSkin = 0;
		for (int i = 0; i < NumCustomSkins; ++i)
		{
			m_skins[i] = NULL;
			m_skinFilenames[i] = NULL;
			m_skinModelnames[i] = NULL;
		}
	}

	// Load the bot profile database
	void BotProfileManager::Init(const char* filename, unsigned int* checksum)
	{
		int dataLength;
		char* dataPointer = (char*)LOAD_FILE_FOR_ME(const_cast<char*>(filename), &dataLength);
		const char* dataFile = dataPointer;

		if (dataFile == NULL)
		{
			if (g_bEnableCSBot)
			{
				CONSOLE_ECHO("WARNING: Cannot access bot profile database '%s'\n", filename);
			}

			return;
		}

		// compute simple checksum
		if (checksum)
		{
			*checksum = ComputeSimpleChecksum((const unsigned char*)dataPointer, dataLength);
		}

		// keep list of templates used for inheritance
		BotProfileList templateList;
		BotProfile defaultProfile;

		// Parse the BotProfile.db into BotProfile instances
		while (true)
		{
			dataFile = SharedParse(dataFile);
			if (!dataFile)
				break;

			char* token = SharedGetToken();

			bool isDefault = (!Q_stricmp(token, "Default"));
			bool isTemplate = (!Q_stricmp(token, "Template"));
			bool isCustomSkin = (!Q_stricmp(token, "Skin"));

			if (isCustomSkin)
			{
				const int BufLen = 64;
				char skinName[BufLen];

				// get skin name
				dataFile = SharedParse(dataFile);
				if (!dataFile)
				{
					CONSOLE_ECHO("Error parsing %s - expected skin name\n", filename);
					FREE_FILE(dataPointer);
					return;
				}

				token = SharedGetToken();
				Q_snprintf(skinName, BufLen, "%s", token);

				// get attribute name
				dataFile = SharedParse(dataFile);
				if (!dataFile)
				{
					CONSOLE_ECHO("Error parsing %s - expected 'Model'\n", filename);
					FREE_FILE(dataPointer);
					return;
				}

				token = SharedGetToken();
				if (Q_stricmp("Model", token))
				{
					CONSOLE_ECHO("Error parsing %s - expected 'Model'\n", filename);
					FREE_FILE(dataPointer);
					return;
				}

				// eat '='
				dataFile = SharedParse(dataFile);
				if (!dataFile)
				{
					CONSOLE_ECHO("Error parsing %s - expected '='\n", filename);
					FREE_FILE(dataPointer);
					return;
				}

				token = SharedGetToken();
				if (Q_strcmp("=", token))
				{
					CONSOLE_ECHO("Error parsing %s - expected '='\n", filename);
					FREE_FILE(dataPointer);
					return;
				}

				// get attribute value
				dataFile = SharedParse(dataFile);
				if (!dataFile)
				{
					CONSOLE_ECHO("Error parsing %s - expected attribute value\n", filename);
					FREE_FILE(dataPointer);
					return;
				}

				token = SharedGetToken();

				const char* decoratedName = GetDecoratedSkinName(skinName, filename);
				bool skinExists = GetCustomSkinIndex(decoratedName) > 0;
				if (m_nextSkin < NumCustomSkins && !skinExists)
				{
					// decorate the name
					m_skins[m_nextSkin] = CloneString(decoratedName);

					// construct the model filename
					m_skinModelnames[m_nextSkin] = CloneString(token);
					m_skinFilenames[m_nextSkin] = new char[Q_strlen(token) * 2 + Q_strlen("models/player//.mdl") + 1];
					Q_sprintf(m_skinFilenames[m_nextSkin], "models/player/%s/%s.mdl", token, token);
					++m_nextSkin;
				}

				// eat 'End'
				dataFile = SharedParse(dataFile);
				if (!dataFile)
				{
					CONSOLE_ECHO("Error parsing %s - expected 'End'\n", filename);
					FREE_FILE(dataPointer);
					return;
				}

				token = SharedGetToken();
				if (Q_strcmp("End", token))
				{
					CONSOLE_ECHO("Error parsing %s - expected 'End'\n", filename);
					FREE_FILE(dataPointer);
					return;
				}

				// it's just a custom skin - no need to do inheritance on a bot profile, etc.
				continue;
			}

			// encountered a new profile
			BotProfile* profile;
			if (isDefault)
			{
				profile = &defaultProfile;
			}
			else
			{
				profile = new BotProfile;
				// always inherit from Default
				*profile = defaultProfile;
			}

			// do inheritance in order of appearance
			if (!isTemplate && !isDefault)
			{
				const BotProfile* inherit = NULL;

				// template names are separated by "+"
				while (true)
				{
					char* c = Q_strchr(token, '+');
					if (c)
						*c = '\0';

					// find the given template name
					for (auto templates : templateList)
					{
						if (!Q_stricmp(templates->GetName(), token))
						{
							inherit = templates;
							break;
						}
					}

					if (inherit == NULL)
					{
						CONSOLE_ECHO("Error parsing '%s' - invalid template reference '%s'\n", filename, token);
						FREE_FILE(dataPointer);
						return;
					}

					// inherit the data
					profile->Inherit(inherit, &defaultProfile);

					if (c == NULL)
						break;

					token = c + 1;
				}
			}

			// get name of this profile
			if (!isDefault)
			{
				dataFile = SharedParse(dataFile);
				if (!dataFile)
				{
					CONSOLE_ECHO("Error parsing '%s' - expected name\n", filename);
					FREE_FILE(dataPointer);
					return;
				}

				profile->m_name = CloneString(SharedGetToken());

				// HACK HACK
				// Until we have a generalized means of storing bot preferences, we're going to hardcode the bot's
				// preference towards silencers based on his name.
				//if (profile->m_name[0] % 2)
				if (RANDOM_LONG(0, 2) == 2)
				{
					profile->m_prefersSilencer = true;
				}
			}

			// read attributes for this profile
			bool isFirstWeaponPref = true;
			while (true)
			{
				// get next token
				dataFile = SharedParse(dataFile);
				if (!dataFile)
				{
					CONSOLE_ECHO("Error parsing %s - expected 'End'\n", filename);
					FREE_FILE(dataPointer);
					return;
				}

				token = SharedGetToken();

				// check for End delimiter
				if (!Q_stricmp(token, "End"))
					break;

				// found attribute name - keep it
				char attributeName[64];
				Q_strcpy(attributeName, token);

				// eat '='
				dataFile = SharedParse(dataFile);
				if (!dataFile)
				{
					CONSOLE_ECHO("Error parsing %s - expected '='\n", filename);
					FREE_FILE(dataPointer);
					return;
				}

				token = SharedGetToken();
				if (Q_strcmp("=", token))
				{
					CONSOLE_ECHO("Error parsing %s - expected '='\n", filename);
					FREE_FILE(dataPointer);
					return;
				}

				// get attribute value
				dataFile = SharedParse(dataFile);
				if (!dataFile)
				{
					CONSOLE_ECHO("Error parsing %s - expected attribute value\n", filename);
					FREE_FILE(dataPointer);
					return;
				}

				token = SharedGetToken();

				// store value in appropriate attribute
				if (!Q_stricmp("Aggression", attributeName))
				{
					profile->m_aggression = Q_atof(token) / 100.0f;
				}
				else if (!Q_stricmp("Skill", attributeName))
				{
					profile->m_skill = Q_atof(token) / 100.0f;
				}
				else if (!Q_stricmp("Skin", attributeName))
				{
					profile->m_skin = Q_atoi(token);

					if (profile->m_skin == 0)
					{
						// Q_atoi() failed - try to look up a custom skin by name
						profile->m_skin = GetCustomSkinIndex(token, filename);
					}
				}
				else if (!Q_stricmp("Teamwork", attributeName))
				{
					profile->m_teamwork = Q_atof(token) / 100.0f;
				}
				else if (!Q_stricmp("Cost", attributeName))
				{
					profile->m_cost = Q_atoi(token);
				}
				else if (!Q_stricmp("VoicePitch", attributeName))
				{
					profile->m_voicePitch = Q_atoi(token);
				}
				else if (!Q_stricmp("VoiceBank", attributeName))
				{
					profile->m_voiceBank = FindVoiceBankIndex(token);
				}
				else if (!Q_stricmp("WeaponPreference", attributeName))
				{
					// weapon preferences override parent prefs
					if (isFirstWeaponPref)
					{
						isFirstWeaponPref = false;
						profile->m_weaponPreferenceCount = 0;
					}

					if (!Q_stricmp(token, "none"))
					{
						profile->m_weaponPreferenceCount = 0;
					}
					else
					{
						if (profile->m_weaponPreferenceCount < BotProfile::MAX_WEAPON_PREFS)
						{
							profile->m_weaponPreference[profile->m_weaponPreferenceCount++] = AliasToWeaponID(token);
						}
					}
				}
				else if (!Q_stricmp("ReactionTime", attributeName))
				{
					profile->m_reactionTime = Q_atof(token) * 1s;

#ifndef GAMEUI_EXPORTS
					// subtract off latency due to "think" update rate.
					// In GameUI, we don't really care.
					profile->m_reactionTime -= g_flBotFullThinkInterval;
#endif // GAMEUI_EXPORTS

				}
				else if (!Q_stricmp("AttackDelay", attributeName))
				{
					profile->m_attackDelay = Q_atof(token) * 1s;
				}
				else if (!Q_stricmp("Difficulty", attributeName))
				{
					// override inheritance
					profile->m_difficultyFlags = 0;

					// parse bit flags
					while (true)
					{
						char* c = Q_strchr(token, '+');
						if (c)
							*c = '\0';

						for (int i = 0; i < NUM_DIFFICULTY_LEVELS; ++i)
						{
							if (!Q_stricmp(BotDifficultyName[i], token))
								profile->m_difficultyFlags |= (1 << i);
						}

						if (c == NULL)
							break;

						token = c + 1;
					}
				}
				else if (!Q_stricmp("Team", attributeName))
				{
					if (!Q_stricmp(token, "T"))
					{
						profile->m_teams = BOT_TEAM_T;
					}
					else if (!Q_stricmp(token, "CT"))
					{
						profile->m_teams = BOT_TEAM_CT;
					}
					else
					{
						profile->m_teams = BOT_TEAM_ANY;
					}
				}
				else
				{
					CONSOLE_ECHO("Error parsing %s - unknown attribute '%s'\n", filename, attributeName);
				}
			}

			if (!isDefault)
			{
				if (isTemplate)
				{
					// add to template list
					templateList.push_back(profile);
				}
				else
				{
					// add profile to the master list
					m_profileList.push_back(profile);
				}
			}
		}

		FREE_FILE(dataPointer);

		// free the templates
		std::for_each(templateList.begin(), templateList.end(), std::default_delete<BotProfile>());
		templateList.clear();
	}

	BotProfileManager::~BotProfileManager()
	{
		Reset();
		std::for_each(m_voiceBanks.begin(), m_voiceBanks.end(), std::default_delete<char[]>());
		m_voiceBanks.clear();
	}

	// Free all bot profiles
	void BotProfileManager::Reset()
	{
		std::for_each(m_profileList.begin(), m_profileList.end(), std::default_delete<BotProfile>());
		m_profileList.clear();

		for (int i = 0; i < NumCustomSkins; ++i)
		{
			if (m_skins[i])
			{
				delete[] m_skins[i];
				m_skins[i] = NULL;
			}
			if (m_skinFilenames[i])
			{
				delete[] m_skinFilenames[i];
				m_skinFilenames[i] = NULL;
			}
			if (m_skinModelnames[i])
			{
				delete[] m_skinModelnames[i];
				m_skinModelnames[i] = NULL;
			}
		}
	}

	// Returns custom skin name at a particular index
	const char* BotProfileManager::GetCustomSkin(int index)
	{
		if (index < FirstCustomSkin || index > LastCustomSkin)
		{
			return NULL;
		}

		return m_skins[index - FirstCustomSkin];
	}

	// Returns custom skin filename at a particular index
	const char* BotProfileManager::GetCustomSkinFname(int index)
	{
		if (index < FirstCustomSkin || index > LastCustomSkin)
		{
			return NULL;
		}

		return m_skinFilenames[index - FirstCustomSkin];
	}

	// Returns custom skin modelname at a particular index
	const char* BotProfileManager::GetCustomSkinModelname(int index)
	{
		if (index < FirstCustomSkin || index > LastCustomSkin)
		{
			return NULL;
		}

		return m_skinModelnames[index - FirstCustomSkin];
	}

	// Looks up a custom skin index by filename-decorated name (will decorate the name if filename is given)
	int BotProfileManager::GetCustomSkinIndex(const char* name, const char* filename)
	{
		const char* skinName = name;
		if (filename)
		{
			skinName = GetDecoratedSkinName(name, filename);
		}

		for (int i = 0; i < NumCustomSkins; ++i)
		{
			if (m_skins[i])
			{
				if (!Q_stricmp(skinName, m_skins[i]))
				{
					return FirstCustomSkin + i;
				}
			}
		}

		return 0;
	}

	// return index of the (custom) bot phrase db, inserting it if needed
	int BotProfileManager::FindVoiceBankIndex(const char* filename)
	{
		int index = 0;
		for (auto phrase : m_voiceBanks)
		{
			if (!Q_stricmp(filename, phrase))
				return index;

			index++;
		}

		m_voiceBanks.push_back(CloneString(filename));
		return index;
	}

	// Return random unused profile that matches the given difficulty level
	const BotProfile* BotProfileManager::GetRandomProfile(BotDifficultyType difficulty, BotProfileTeamType team) const
	{
		BotProfileList::const_iterator iter;

		// count up valid profiles
		int validCount = 0;
		for (auto profile : m_profileList)
		{
			if (profile->IsDifficulty(difficulty) && !UTIL_IsNameTaken(profile->GetName()) && profile->IsValidForTeam(team))
				validCount++;
		}

		if (validCount == 0)
			return nullptr;

		// select one at random
		int which = RANDOM_LONG(0, validCount - 1);
		for (auto profile : m_profileList)
		{
			if (profile->IsDifficulty(difficulty) && !UTIL_IsNameTaken(profile->GetName()) && profile->IsValidForTeam(team))
			{
				if (which-- == 0)
					return profile;
			}
		}

		return nullptr;
	}
}